package com.frank.oj.manager;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.UnicodeUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.frank.model.entity.common.File;
import com.frank.model.entity.judge.Judge;
import com.frank.model.entity.problem.Problem;
import com.frank.oj.config.NacosSwitchConfig;
import com.frank.oj.config.SwitchConfig;
import com.frank.oj.config.WebConfig;
import com.frank.oj.constant.Constants;
import com.frank.oj.model.entity.RecentContest;
import com.frank.oj.model.vo.*;
import com.frank.oj.service.entity.common.AnnouncementEntityService;
import com.frank.oj.service.entity.common.FileEntityService;
import com.frank.oj.service.entity.judge.JudgeEntityService;
import com.frank.oj.service.entity.contest.ContestEntityService;
import com.frank.oj.service.entity.problem.ProblemEntityService;
import com.frank.oj.service.entity.user.UserRecordEntityService;
import com.frank.oj.utils.RedisUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author frank
 * @Date 2023/12/20
 */
@Component
public class HomeManager {
    @Resource
    private ContestEntityService contestEntityService;
    @Resource
    private FileEntityService fileEntityService;
    @Resource
    private UserRecordEntityService userRecordEntityService;
    @Resource
    private ProblemEntityService problemEntityService;
    @Resource
    private NacosSwitchConfig nacosSwitchConfig;
    @Resource
    private AnnouncementEntityService announcementEntityService;
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private JudgeEntityService judgeEntityService;

    public List<ContestVO> getRecentContest() {
        return contestEntityService.getWithinNext14DaysContest();
    }

    public List<HashMap<String, Object>> getHomeCarousel() {
        List<File> fileList = fileEntityService.queryCarouselFileList();

        List<HashMap<String, Object>> apiList = fileList.stream().map(file -> {
            HashMap<String, Object> param = new HashMap<>();
            param.put("id", file.getId());
            param.put("url", Constants.File.IMG_API.getPath() + file.getName());
            return param;
        }).collect(Collectors.toList());

        return apiList;
    }

    public List<ACMRankVO> getRecentSevenACRank() {
        return userRecordEntityService.getRecentSevenACRank();
    }

    public List<RecentUpdatedProblemVO> getRecentUpdatedProblemList() {
        LambdaQueryWrapper<Problem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(Problem::getId, Problem::getProblemId,
                Problem::getTitle, Problem::getType,
                Problem::getGmtModified, Problem::getGmtCreate);
        queryWrapper.eq(Problem::getAuth, 1);
        queryWrapper.eq(Problem::getIsGroup, false);
        queryWrapper.orderByDesc(Problem::getGmtCreate);
        queryWrapper.last("limit 10");

        List<Problem> problemList = problemEntityService.list(queryWrapper);

        if (!CollectionUtils.isEmpty(problemList)) {
            return problemList.stream().map(this::convertUpdatedProblemVO).collect(Collectors.toList());
        }
        return new ArrayList<>();

    }

    private RecentUpdatedProblemVO convertUpdatedProblemVO(Problem problem) {
        return RecentUpdatedProblemVO.builder()
                .problemId(problem.getProblemId())
                .id(problem.getId())
                .title(problem.getTitle())
                .gmtCreate(problem.getGmtCreate())
                .gmtModified(problem.getGmtModified())
                .type(problem.getType())
                .build();
    }

    public Map<Object, Object> getWebConfig() {
        SwitchConfig switchConfig = nacosSwitchConfig.getSwitchConfig();
        WebConfig webConfig = nacosSwitchConfig.getWebConfig();
        return MapUtil.builder()
                .put("baseUrl", UnicodeUtil.toString(webConfig.getBaseUrl()))
                .put("name", UnicodeUtil.toString(webConfig.getName()))
                .put("shortName", UnicodeUtil.toString(webConfig.getShortName()))
                .put("register", webConfig.getRegister())
                .put("recordName", UnicodeUtil.toString(webConfig.getRecordName()))
                .put("recordUrl", UnicodeUtil.toString(webConfig.getRecordUrl()))
                .put("description", UnicodeUtil.toString(webConfig.getDescription()))
                .put("email", UnicodeUtil.toString(webConfig.getEmailUsername()))
                .put("projectName", UnicodeUtil.toString(webConfig.getProjectName()))
                .put("projectUrl", UnicodeUtil.toString(webConfig.getProjectUrl()))
                .put("openPublicDiscussion", switchConfig.getOpenPublicDiscussion())
                .put("openGroupDiscussion", switchConfig.getOpenGroupDiscussion())
                .put("openContestComment", switchConfig.getOpenContestComment())
                .map();
    }

    public IPage<AnnouncementVO> getCommonAnnouncement(Integer limit, Integer currentPage) {
        if (currentPage == null || currentPage < 1) {
            currentPage = 1;
        }
        if (limit == null || limit < 1) {
            limit = 10;
        }
        return announcementEntityService.getAnnouncementList(limit, currentPage, true);
    }

    private final static String SUBMISSION_STATISTICS_KEY = "home_submission_statistics";

    /**
     * 获取最近一周的提交统计信息
     *
     * @param forceRefresh 是否强制刷新
     * @return 提交统计信息对象
     */
    public SubmissionStatisticsVO getLastWeekSubmissionStatistics(Boolean forceRefresh) {
        // 从Redis中获取提交统计信息对象
        SubmissionStatisticsVO submissionStatisticsVO = (SubmissionStatisticsVO) redisUtils.get(SUBMISSION_STATISTICS_KEY);

        // 判断是否为root用户
        boolean isRoot = SecurityUtils.getSubject().hasRole("root");
        // 判断是否需要强制刷新
        forceRefresh = forceRefresh && isRoot;

        if (submissionStatisticsVO == null || forceRefresh) {
            // 计算一周前的时间点
            DateTime dateTime = DateUtil.offsetDay(new Date(), -6);
            // 格式化时间为字符串
            String strTime = DateFormatUtils.format(dateTime, "yyyy-MM-dd") + "00:00:00";

            LambdaQueryWrapper<Judge> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.select(Judge::getSubmitId, Judge::getStatus, Judge::getGmtCreate);
            queryWrapper.apply("UNIX_TIMESTAMP(gmt_create) >= UNIX_TIMESTAMP('\" + strTime + \"')");

            List<Judge> judgeList = judgeEntityService.list(queryWrapper);

            submissionStatisticsVO = buildSubmissionStatisticsVo(judgeList);

            redisUtils.set(SUBMISSION_STATISTICS_KEY, submissionStatisticsVO, 60 * 30);
        }
        return submissionStatisticsVO;
    }

    /**
     * 构建提交统计数据的VO对象
     *
     * @param judgeList 判断结果列表
     * @return 提交统计数据的VO对象
     */
    private SubmissionStatisticsVO buildSubmissionStatisticsVo(List<Judge> judgeList) {
        long acTodayCount = 0; // 今天通过判断的提交数量
        long acOneDayAgoCount = 0; // 昨天通过判断的提交数量
        long acTwoDaysAgoCount = 0; // 前天通过判断的提交数量
        long acThreeDaysAgoCount = 0; // 大前天通过判断的提交数量
        long acFourDaysAgoCount = 0; // 更早前天通过判断的提交数量
        long acFiveDaysAgoCount = 0; // 更早前天通过判断的提交数量
        long acSixDaysAgoCount = 0; // 更更早前天通过判断的提交数量

        long totalTodayCount = 0; // 今天总的提交数量
        long totalOneDayAgoCount = 0; // 昨天总的提交数量
        long totalTwoDaysAgoCount = 0; // 前天总的提交数量
        long totalThreeDaysAgoCount = 0; // 大前天总的提交数量
        long totalFourDaysAgoCount = 0; // 更早前天总的提交数量
        long totalFiveDaysAgoCount = 0; // 更早前天总的提交数量
        long totalSixDaysAgoCount = 0; // 更更早前天总的提交数量;

        Date date = new Date();
        String todayStr = DateUtil.format(date, "MM-dd");
        String oneDayAgoStr = DateFormatUtils.format(DateUtil.offsetDay(date, -1), "MM-dd");
        String twoDaysAgoStr = DateFormatUtils.format(DateUtil.offsetDay(date, -2), "MM-dd");
        String threeDaysAgoStr = DateFormatUtils.format(DateUtil.offsetDay(date, -3), "MM-dd");
        String fourDaysAgoStr = DateFormatUtils.format(DateUtil.offsetDay(date, -4), "MM-dd");
        String fiveDaysAgoStr = DateFormatUtils.format(DateUtil.offsetDay(date, -5), "MM-dd");
        String sixDaysAgoStr = DateFormatUtils.format(DateUtil.offsetDay(date, -6), "MM-dd");

        if (!CollectionUtils.isEmpty(judgeList)) {
            // 对judgeList按照月份进行分组
            Map<String, List<Judge>> map = judgeList.stream()
                    .collect(
                            Collectors.groupingBy(
                                    o -> DateUtil.format(o.getGmtCreate(), "MM-dd"))
                    );
            for (Map.Entry<String, List<Judge>> entry : map.entrySet()) {
                // 如果月份和当天月份相同
                if (Objects.equals(entry.getKey(), todayStr)) {
                    // 总提交数量累加list的大小
                    totalTodayCount += entry.getValue().size();
                    // 筛选出状态为已接受的judge数量并累加
                    long count = entry.getValue()
                            .parallelStream()
                            .filter(judge -> Objects.equals(judge.getStatus(), Constants.Judge.STATUS_ACCEPTED.getStatus()))
                            .count();
                    acTodayCount += count;
                }
                // 如果月份和昨天月份相同
                else if (Objects.equals(entry.getKey(), oneDayAgoStr)) {
                    // 总提交数量累加list的大小
                    totalOneDayAgoCount += entry.getValue().size();
                    // 筛选出状态为已接受的judge数量并累加
                    long count = entry.getValue()
                            .parallelStream()
                            .filter(judge -> Objects.equals(judge.getStatus(), Constants.Judge.STATUS_ACCEPTED.getStatus()))
                            .count();
                    acOneDayAgoCount += count;
                }
                // 如果月份和前天月份相同
                else if (Objects.equals(entry.getKey(), twoDaysAgoStr)) {
                    // 总提交数量累加list的大小
                    totalTwoDaysAgoCount += entry.getValue().size();
                    // 筛选出状态为已接受的judge数量并累加
                    long count = entry.getValue()
                            .parallelStream()
                            .filter(judge -> Objects.equals(judge.getStatus(), Constants.Judge.STATUS_ACCEPTED.getStatus()))
                            .count();
                    acTwoDaysAgoCount += count;
                }
                // 如果月份和前天月份相同
                else if (Objects.equals(entry.getKey(), threeDaysAgoStr)) {
                    // 总提交数量累加list的大小
                    totalThreeDaysAgoCount += entry.getValue().size();
                    // 筛选出状态为已接受的judge数量并累加
                    long count = entry.getValue()
                            .parallelStream()
                            .filter(judge -> Objects.equals(judge.getStatus(), Constants.Judge.STATUS_ACCEPTED.getStatus()))
                            .count();
                    acThreeDaysAgoCount += count;
                }
                // 如果月份和前前天月份相同
                else if (Objects.equals(entry.getKey(), fourDaysAgoStr)) {
                    // 总提交数量累加list的大小
                    totalFourDaysAgoCount += entry.getValue().size();
                    // 筛选出状态为已接受的judge数量并累加
                    long count = entry.getValue()
                            .parallelStream()
                            .filter(judge -> Objects.equals(judge.getStatus(), Constants.Judge.STATUS_ACCEPTED.getStatus()))
                            .count();
                    acFourDaysAgoCount += count;
                }
                // 如果月份和前前天月份相同
                else if (Objects.equals(entry.getKey(), fiveDaysAgoStr)) {
                    // 总提交数量累加list的大小
                    totalFiveDaysAgoCount += entry.getValue().size();
                    // 筛选出状态为已接受的judge数量并累加
                    long count = entry.getValue()
                            .parallelStream()
                            .filter(judge -> Objects.equals(judge.getStatus(), Constants.Judge.STATUS_ACCEPTED.getStatus()))
                            .count();
                    acFiveDaysAgoCount += count;
                }
                // 如果月份和前前天月份相同
                else if (Objects.equals(entry.getKey(), sixDaysAgoStr)) {
                    // 总提交数量累加list的大小
                    totalSixDaysAgoCount += entry.getValue().size();
                    // 筛选出状态为已接受的judge数量并累加
                    long count = entry.getValue()
                            .parallelStream()
                            .filter(judge -> Objects.equals(judge.getStatus(), Constants.Judge.STATUS_ACCEPTED.getStatus()))
                            .count();
                    acSixDaysAgoCount += count;
                }
            }
        }


        SubmissionStatisticsVO submissionStatisticsVO = new SubmissionStatisticsVO();
        submissionStatisticsVO.setDateStrList(Arrays.asList(
                sixDaysAgoStr,
                fiveDaysAgoStr,
                fourDaysAgoStr,
                threeDaysAgoStr,
                twoDaysAgoStr,
                oneDayAgoStr,
                todayStr));

        submissionStatisticsVO.setAcCountList(Arrays.asList(
                acSixDaysAgoCount,
                acFiveDaysAgoCount,
                acFourDaysAgoCount,
                acThreeDaysAgoCount,
                acTwoDaysAgoCount,
                acOneDayAgoCount,
                acTodayCount));

        submissionStatisticsVO.setTotalCountList(Arrays.asList(
                totalSixDaysAgoCount,
                totalFiveDaysAgoCount,
                totalFourDaysAgoCount,
                totalThreeDaysAgoCount,
                totalTwoDaysAgoCount,
                totalOneDayAgoCount,
                totalTodayCount));

        return submissionStatisticsVO;
    }

    public Map<String, Object> getRecentOtherContest() {
        String redisKey = Constants.Schedule.RECENT_OTHER_CONTEST.getCode();
        // 从redis获取比赛列表
        return (Map<String, Object>) redisUtils.get(redisKey);
    }
}
